<?php

require("Gomoku.php");

/*
$b = new Gomoku(1);
$w = new Gomoku(7);
$res = vs($b, $w);
$mat = $res["matrix"];
for ($i = 0; $i < 15; $i++) {
    echo join(array_slice($mat, $i * 15, 15), "| "), "\n";
}
*/

function train()
{
    // 载入32个选手
    $gs = [];
    for ($i = 0; $i < 32; $i++) {
        $gs[] = new Gomoku();
    }

    // 训练 50 代
    for ($i = 0; $i < 50; $i++) {
        $gs = ga(select($gs));
        save($gs, $i);
    }
}

// 保存训练网络
function save(array $gs, int $i)
{
    $dir = "train_net" . DIRECTORY_SEPARATOR . $i;
    if (!is_dir($dir)) {
        mkdir($dir);
    }
    $j = 0;
    $score = [];
    foreach ($gs as $g) {
        fann_save($g->nn, $dir . DIRECTORY_SEPARATOR . "$j.net");
        $score[] = [$j => $g->score];
        $j++;
    }
    file_put_contents($dir . DIRECTORY_SEPARATOR . "score.json", json_encode($score));
}

// 32个选手, 4个一组,双循环比赛, 返回成绩较好的一半
function select($gs) {
    $score = array_pad([], 32, 0);
    for ($i = 0; $i < 8; $i++) {
        $g = [];
        for ($j = 0; $j < 4; $j++) {
            $g[] = $gs[$i * 4 + $j];
        }

        for ($k = 0; $k < 4; $k++) {
            for ($l = 0; $l < 4; $l++) {
                if ($k != $l) {
                    $s = vs(($g[$l])->setColor(Gomoku::BLACK), ($g[$k])->setColor(Gomoku::WHITE));
                    echo $i * 4 + $l, " vs: ", $i * 4 + $k, "\n";
                    $score[$i * 4 + $l] += $s[0];
                    $score[$i * 4 + $k] += $s[1];
                }
            }
        }
    }

    for ($i = 0; $i < 32; $i++) {
        ($gs[$i])->score = $score[$i];
    }

    usort($gs, function ($g1, $g2) {
        return $g2->score - $g1->score;
    });

    // 销毁一半
    for ($i = 16; $i < 32; $i++) {
        ($gs[$i])->destory();
        unset($gs[$i]);
    }

    // 返回剩下的一半
    return $gs;
}

/**
 * 通过 16 个选手, 繁殖 32 个后代
 * 保留两个, 有一个发生突变
 * (fann 读取和设置参数非常慢, 所以这一步相当耗时)
 */
function ga(array $gs)
{
    $next = [];
    // 保留
    for ($i = 0; $i < 2; $i++) {
        $g1 = $gs[$i];
        $g2= new Gomoku();
        $next[] = $g1;
        $next[] = $g2->setWeights($g1->getWeights());
    }

    // 杂交
    for ($i = 2; $i < 8; $i++) {
        $g1 = $gs[$i * 2 - 2];
        $g2 = $gs[$i * 2 - 1];
        $w1 = $g1->getWeights();
        $w2 = $g2->getWeights();
        $half = intval(count($w1) / 2);
        $w1s = array_chunk($w1, $half);
        $w2s = array_chunk($w2, $half);
        $w12 = array_merge($w1s[0], $w2s[1]);
        $w21 = array_merge($w2s[0], $w1s[1]);
        $next[] = (new Gomoku())->setWeights($w12);
        $next[] = (new Gomoku())->setWeights($w12);
        $next[] = (new Gomoku())->setWeights($w21);
        $next[] = (new Gomoku())->setWeights($w21);
    }

    // 突变
    $index = rand(4, 31);
    ($next[$index])->nn = $g2->randnn();

    return $next;
}


/**
 * 一次对局
 * 黑胜积 5 分, 和积 3 分, 负积 0 分
 * 白胜积 100 分, 和积 50 分, 负积 1 分
 */
function vs(Gomoku $black, Gomoku $white, $detail = false)
{
    $input = Gomoku::newGame();
    for ($i = 0; $i < 15 * 15; $i++) {
        $win = Gomoku::winner($input);
        if ($win == 0) {
            if (Gomoku::turn($input)) {
                $input = $white->next($input);
            } else {
                $input = $black->next($input);
            }
        } elseif ($win == 1) {
            $score = [3, 50];
            break;
        } elseif ($win == 2) {
            $score = [5, 1];
            break;
        } elseif ($win == 3) {
            $score = [0, 100];
            break;
        }
    }

    if ($detail) {
        return [
            "matrix" => $input,
            "step" => $i,
            "win" => $win,
            "score" => $score,
        ];
    } else {
        return $score;
    }
}
